/*
 * Copyright (C) 2014-2016 Qiujuer <qiujuer@live.cn>
 * WebSite http://www.qiujuer.net
 * Author qiujuer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.qiujuer.genius.ui.widget;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextTool;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import net.qiujuer.genius.ui.AttrValue;
import net.qiujuer.genius.ui.ResourceTable;
import net.qiujuer.genius.ui.Ui;
import net.qiujuer.genius.ui.drawable.LoadingCircleDrawable;
import net.qiujuer.genius.ui.drawable.LoadingDrawable;
import net.qiujuer.genius.ui.drawable.LoadingLineDrawable;

import java.io.IOException;

/**
 * This is loading view,
 * You can use it to show progress bar.
 * <p>
 * <p><strong>XML attributes</strong></p>
 * <p> @since 2020-05-13
 */
@SuppressWarnings("WeakerAccess")
public class Loading extends Component {
    /**
     * 圆形样式
     */
    public static final int STYLE_CIRCLE = 1;
    /**
     * 线性样式
     */
    public static final int STYLE_LINE = 2;
    private boolean mNeedRun;
    private LoadingDrawable mLoadingDrawable;
    private boolean mAutoRun;
    private String mForegroundColor = "gForegroundColor";

    public Loading(Context context) {
        super(context);
        init(null);
        initListeners(this);
    }

    public Loading(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(attrSet);
        initListeners(this);
    }

    public Loading(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
        initListeners(this);
    }

    public Loading(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        init(attrSet);
        initListeners(this);
    }

    private void init(AttrSet attrs) {
        final Context context = getContext();
        final Display display = DisplayManager.getInstance().getDefaultDisplay(context).get();
        final ResourceManager resource = getResourceManager();
        if (attrs == null) {
            // default we init a circle style loading drawable
            setProgressStyle(STYLE_CIRCLE);
            return;
        }
        final float density = display.getAttributes().densityPixels;

        // default size 2dp
        final int baseSize = (int) (density * 2);

        // Load attributes
//        if (attrs != null) {
            int bgLineSize = AttrValue.getDimension(attrs, "gBackgroundLineSize", baseSize);
            int fgLineSize = AttrValue.getDimension(attrs, "gForegroundLineSize", baseSize);
            int bgColor = 0; // transparent color
            try {
                bgColor = AttrValue.getColorValue(attrs, "gBackgroundColor", Color.getIntColor("#e0e0e0"));
            } catch (Exception e) {
                bgColor = 0;
            }

            int fgColor = Color.BLACK.getValue();
            int[] fgColorArray = null;
            try {
                fgColor = AttrValue.getColorValue(attrs, mForegroundColor, "").getValue();
            } catch (Exception ignored) {
                int fgColorId = AttrValue.getResourceId(attrs, mForegroundColor, ResourceTable.Strarray_g_default_loading_fg);
                // Check for IDE preview render
                String[] taColor;
                try {
                    taColor = resource.getElement(fgColorId).getStringArray();
                    int length = taColor.length;
                    if (length > 0) {
                        fgColorArray = new int[length];
                        for (int i = 0; i < length; i++) {
                            if (TextTool.isNullOrEmpty(taColor[i])) {
                                fgColorArray[i] = Color.BLACK.getValue();
                            } else {
                                fgColorArray[i] = Color.getIntColor(taColor[i]);
                            }
                        }
                    } else {
                        String value = attrs.getAttr(mForegroundColor).get().getStringValue();
                        if (value.contains(":")) {
                            value = value.substring(0, value.indexOf(":"));
                            try {
                                switch (value) {
                                    case "$color":
                                        fgColor = AttrValue.getColorValue(attrs, mForegroundColor, 0);
                                        break;
                                    case "$strarray":
                                    default:
                                        fgColorArray = getArrayElement(ResourceTable.Strarray_g_default_loading_fg);
                                        break;
                                }
                            } catch (Exception e) {
                                showDebug(e.getMessage());
                                fgColorArray = getArrayElement(ResourceTable.Strarray_g_default_loading_fg);
                            }
                        }
                    }
                } catch (IOException e) {
                    showDebug(e.getMessage());
                } catch (NotExistException e) {
                    showDebug(e.getMessage());
                } catch (WrongTypeException e) {
                    showDebug(e.getMessage());
                }
            }
            int style = AttrValue.get(attrs, "gProgressStyle", 1);
            boolean autoRun = AttrValue.get(attrs, "gAutoRun", true);
            float progress = AttrValue.get(attrs, "gProgressFloat", 0.0f);
            setProgressStyle(style);
            setAutoRun(autoRun);
            setProgress(progress);
            setBackgroundLineSize(bgLineSize);
            setForegroundLineSize(fgLineSize);
            setBackgroundColor(bgColor);
            if (fgColorArray == null) {
                setForegroundColor(fgColor);
            } else {
                setForegroundColor(fgColorArray);
            }
//        }
    }

    private void showDebug(Object o) {
        HiLog.debug(new HiLogLabel(HiLog.LOG_APP, 0x011, "tag"), "%{public}s", String.valueOf(o));
    }

    private int[] getArrayElement(int resId) {
        try {
            String[] taColor = getResourceManager().getElement(resId).getStringArray();
            int length = taColor.length;
            int[] fgColorArray = new int[length];
            if (length > 0) {
                for (int i = 0; i < length; i++) {
                    fgColorArray[i] = Color.getIntColor(taColor[i]);
                }
            }
            return fgColorArray;
        } catch (IOException e) {
            showDebug(e.getMessage());
        } catch (NotExistException e) {
            showDebug(e.getMessage());
        } catch (WrongTypeException e) {
            showDebug(e.getMessage());
        }
        return new int[0];
    }

    /**
     * Start the loading animation
     */
    public void start() {
        mLoadingDrawable.start();
        mNeedRun = false;
    }

    /**
     * Stop the loading animation
     */
    public void stop() {
        mLoadingDrawable.stop();
        mNeedRun = false;
    }

    /**
     * Check the loading is Running state
     *
     * @return Loading is Running
     */
    public boolean isRunning() {
        return mLoadingDrawable.isRunning();
    }

    /**
     * Set the Background line size,
     * the unit is px, if you set dp plx use {@link net.qiujuer.genius.ui.Ui#dipToPx(int, float, DisplayAttributes)}  }
     *
     * @param size Background line size
     */
    public void setBackgroundLineSize(int size) {
        mLoadingDrawable.setBackgroundLineSize(size);
        invalidate();
        postLayout();
    }

    /**
     * Set the Foreground line size,
     * the unit is px, if you set dp plx use   }
     *
     * @param size Foreground line size
     */
    public void setForegroundLineSize(int size) {
        mLoadingDrawable.setForegroundLineSize(size);
        invalidate();
        postLayout();
    }

    /**
     * Get the background line size
     *
     * @return the size unit is px
     */
    public float getBackgroundLineSize() {
        return mLoadingDrawable.getBackgroundLineSize();
    }

    /**
     * Get the foreground line size
     *
     * @return the size unit is px
     */
    public float getForegroundLineSize() {
        return mLoadingDrawable.getForegroundLineSize();
    }

    /**
     * Set the background color, eg: "#0xffffff"
     * else you @see {@link #setBackgroundColorRes(int)}
     *
     * @param color color value
     */
    public void setBackgroundColor(int color) {
        mLoadingDrawable.setBackgroundColor(color);
        invalidate();
    }

    /**
     * Set the background color by resource id
     *
     * @param colorRes Color resource id
     */
    public void setBackgroundColorRes(int colorRes) {
        try {
            int color = getResourceManager().getElement(colorRes).getColor();
            if (color <= 0) {
                setBackgroundColor(0);
            } else {
                setBackgroundColor(color);
            }
        } catch (IOException e) {
            showDebug(e.getMessage());
        } catch (NotExistException e) {
            showDebug(e.getMessage());
        } catch (WrongTypeException e) {
            showDebug(e.getMessage());
        }
    }

    /**
     * Get background color value
     *
     * @return Color
     */
    public int getBackgroundColor() {
        return mLoadingDrawable.getBackgroundColor();
    }

    /**
     * Set the Foreground color, eg: "#0xffffff"
     * else you can ues {@link #setForegroundColor(int[])}
     *
     * @param color color value
     */
    public void setForegroundColor(int color) {
        setForegroundColor(new int[]{color});
    }

    /**
     * Set the Foreground color by color array
     *
     * @param colors Color array
     */
    public void setForegroundColor(int[] colors) {
        mLoadingDrawable.setForegroundColor(colors);
        invalidate();
    }

    /**
     * Get the Foreground color array
     *
     * @return Color array
     */
    public int[] getForegroundColor() {
        return mLoadingDrawable.getForegroundColor();
    }

    /**
     * Get the loading progress value, default "0"
     *
     * @return Progress value
     */
    public float getProgress() {
        return mLoadingDrawable.getProgress();
    }

    /**
     * Set the loading Progress, the default "0";
     * If you set the value, the loading will stop animation
     * The Progress between 0 to 1 float.
     *
     * @param progress Progress
     */
    public void setProgress(float progress) {
        mLoadingDrawable.setProgress(progress);
        invalidate();
    }

    /**
     * Set the run type, default is true
     * If set "True", the loading will auto running after onAttachedToWindow()
     * If set "False", you can call {@link #start()} to running animation
     * <p>
     * You can only set the method before onAttachedToWindow() method call.
     *
     * @param autoRun Auto run
     */
    public void setAutoRun(boolean autoRun) {
        mAutoRun = autoRun;
    }

    /**
     * Get the loading run type
     *
     * @return Bool
     */
    public boolean isAutoRun() {
        return mAutoRun;
    }

    /**
     * Change the loading style
     * You can set {@link #STYLE_CIRCLE} or {@link #STYLE_LINE} parameters
     *
     * @param style {@link #STYLE_CIRCLE} or {@link #STYLE_LINE}
     */
    public void setProgressStyle(int style) {
//        LoadingDrawable drawable = null;
        if (style == STYLE_CIRCLE) {
            LoadingDrawable drawable = new LoadingCircleDrawable(this, Ui.getDimensionPixelOffset(getContext(), ResourceTable.Integer_g_loading_minSize),
                Ui.getDimensionPixelOffset(getContext(), ResourceTable.Integer_g_loading_maxSize));
            setLoadingDrawable(drawable);
        } else if (style == STYLE_LINE) {
            LoadingDrawable drawable = new LoadingLineDrawable(this);
            setLoadingDrawable(drawable);
        }
//        setLoadingDrawable(drawable);
    }

    /**
     * In this we set LoadingDrawable really
     *
     * @param drawable {@link LoadingDrawable}
     */
    protected void setLoadingDrawable(LoadingDrawable drawable) {
        if (drawable == null) {
            throw new NullPointerException("LoadingDrawable is null, You can only set the STYLE_CIRCLE and STYLE_LINE parameters.");
        } else {
            mLoadingDrawable = drawable;
            addDrawTask(drawable, DrawTask.BETWEEN_CONTENT_AND_FOREGROUND);
            invalidate();
            postLayout();
        }
    }

    private void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        int heightMode = EstimateSpec.getMode(heightMeasureSpec);
        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        int iHeight = mLoadingDrawable.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom();
        int iWidth = mLoadingDrawable.getIntrinsicWidth() + getPaddingLeft() + getPaddingRight();
        int measuredWidth;
        int measuredHeight;
        if (widthMode == EstimateSpec.PRECISE) {
            measuredWidth = widthSize + getPaddingLeft() + getPaddingRight();
        } else if (widthMode == EstimateSpec.NOT_EXCEED) {
            measuredWidth = Math.min(widthSize, iWidth);
        } else {
            measuredWidth = iWidth;
        }

        if (heightMode == EstimateSpec.PRECISE) {
            measuredHeight = heightSize + getPaddingTop() + getPaddingBottom();
        } else if (heightMode == EstimateSpec.NOT_EXCEED) {
            measuredHeight = Math.min(heightSize, iHeight);
        } else {
            measuredHeight = iHeight;
        }
        setEstimatedSize(EstimateSpec.getChildSizeWithMode(measuredWidth, measuredWidth, widthMode), EstimateSpec.getChildSizeWithMode(measuredHeight, measuredHeight, heightMode));
    }

    private void onSizeChanged(int w, int h) {
        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();
        final int paddingRight = getPaddingRight();
        final int paddingBottom = getPaddingBottom();
        mLoadingDrawable.onBoundsChange(paddingLeft, paddingTop, w - paddingRight, h - paddingBottom);
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        changeRunStateByVisibility(visibility);
    }

    private void initListeners(Component component) {
        component.setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
                changeRunStateByVisibility(component.getVisibility());
                onAttachedToWindow();
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
                changeRunStateByVisibility(component.getVisibility());
                mLoadingDrawable.stop();
            }
        });
        setEstimateSizeListener(new EstimateSizeListener() {
            @Override
            public boolean onEstimateSize(int i, int i1) {
                onMeasure(i, i1);
                onSizeChanged(getEstimatedWidth(), getEstimatedHeight());
                return true;
            }
        });
    }

    private void changeRunStateByVisibility(int visibility) {
        if (mLoadingDrawable == null) {
            return;
        }
        if (visibility == VISIBLE) {
            if (mNeedRun) {
                start();
            }
        } else {
            if (mLoadingDrawable.isRunning()) {
                mNeedRun = true;
                mLoadingDrawable.stop();
            }
        }
    }

    private void onAttachedToWindow() {
        if (mAutoRun && mLoadingDrawable.getProgress() == 0) {
            if (getVisibility() == VISIBLE) {
                mLoadingDrawable.start();
            } else {
                mNeedRun = true;
            }
        }
    }
}
